import Page from "./page.js";
import ContentParser from "./parser.js";
import EventEmitter from "event-emitter";
import Hook from "../utils/hook.js";
import Queue from "../utils/queue.js";
import { requestIdleCallback } from "../utils/utils.js";
import { OverflowContentError } from "./renderresult.js";

const MAX_PAGES = false;
const MAX_LAYOUTS = false;

const TEMPLATE = `<div class="pagedjs_page">
	<div class="pagedjs_sheet">
		<div class="pagedjs_bleed pagedjs_bleed-top">
			<div class="pagedjs_marks-crop"></div>
			<div class="pagedjs_marks-middle">
				<div class="pagedjs_marks-cross"></div>
			</div>
			<div class="pagedjs_marks-crop"></div>
		</div>
		<div class="pagedjs_bleed pagedjs_bleed-bottom">
			<div class="pagedjs_marks-crop"></div>
			<div class="pagedjs_marks-middle">
				<div class="pagedjs_marks-cross"></div>
			</div>
			<div class="pagedjs_marks-crop"></div>
		</div>
		<div class="pagedjs_bleed pagedjs_bleed-left">
			<div class="pagedjs_marks-crop"></div>
			<div class="pagedjs_marks-middle">
				<div class="pagedjs_marks-cross"></div>
			</div>
			<div class="pagedjs_marks-crop"></div>
		</div>
		<div class="pagedjs_bleed pagedjs_bleed-right">
			<div class="pagedjs_marks-crop"></div>
			<div class="pagedjs_marks-middle">
				<div class="pagedjs_marks-cross"></div>
			</div>
			<div class="pagedjs_marks-crop"></div>
		</div>
		<div class="pagedjs_pagebox">
			<div class="pagedjs_margin-top-left-corner-holder">
				<div class="pagedjs_margin pagedjs_margin-top-left-corner">
					<div class="pagedjs_margin-content"></div>
				</div>
			</div>
			<div class="pagedjs_margin-top">
				<div class="pagedjs_margin pagedjs_margin-top-left">
					<div class="pagedjs_margin-content"></div>
				</div>
				<div class="pagedjs_margin pagedjs_margin-top-center">
					<div class="pagedjs_margin-content"></div>
				</div>
				<div class="pagedjs_margin pagedjs_margin-top-right">
					<div class="pagedjs_margin-content"></div>
				</div>
			</div>
			<div class="pagedjs_margin-top-right-corner-holder">
				<div class="pagedjs_margin pagedjs_margin-top-right-corner">
					<div class="pagedjs_margin-content"></div>
				</div>
			</div>
			<div class="pagedjs_margin-right">
				<div class="pagedjs_margin pagedjs_margin-right-top">
					<div class="pagedjs_margin-content"></div>
				</div>
				<div class="pagedjs_margin pagedjs_margin-right-middle">
					<div class="pagedjs_margin-content"></div>
				</div>
				<div class="pagedjs_margin pagedjs_margin-right-bottom">
					<div class="pagedjs_margin-content"></div>
				</div>
			</div>
			<div class="pagedjs_margin-left">
				<div class="pagedjs_margin pagedjs_margin-left-top">
					<div class="pagedjs_margin-content"></div>
				</div>
				<div class="pagedjs_margin pagedjs_margin-left-middle">
					<div class="pagedjs_margin-content"></div>
				</div>
				<div class="pagedjs_margin pagedjs_margin-left-bottom">
					<div class="pagedjs_margin-content"></div>
				</div>
			</div>
			<div class="pagedjs_margin-bottom-left-corner-holder">
				<div class="pagedjs_margin pagedjs_margin-bottom-left-corner">
					<div class="pagedjs_margin-content"></div>
				</div>
			</div>
			<div class="pagedjs_margin-bottom">
				<div class="pagedjs_margin pagedjs_margin-bottom-left">
					<div class="pagedjs_margin-content"></div>
				</div>
				<div class="pagedjs_margin pagedjs_margin-bottom-center">
					<div class="pagedjs_margin-content"></div>
				</div>
				<div class="pagedjs_margin pagedjs_margin-bottom-right">
					<div class="pagedjs_margin-content"></div>
				</div>
			</div>
			<div class="pagedjs_margin-bottom-right-corner-holder">
				<div class="pagedjs_margin pagedjs_margin-bottom-right-corner">
					<div class="pagedjs_margin-content"></div>
				</div>
			</div>
			<div class="pagedjs_area">
				<div class="pagedjs_page_content"></div>
				<div class="pagedjs_footnote_area">
					<div class="pagedjs_footnote_content pagedjs_footnote_empty">
						<div class="pagedjs_footnote_inner_content"></div>
					</div>
				</div>
			</div>
		</div>
	</div>
</div>`;

/**
 * 将文本切割成流
 * @class Chunker
 */
class Chunker {
	constructor(content, renderTo, options) {
		// this.preview = preview;

		this.settings = options || {};

		this.hooks = {};
		this.hooks.beforeParsed = new Hook(this);
		this.hooks.filter = new Hook(this);
		this.hooks.afterParsed = new Hook(this);
		this.hooks.beforePageLayout = new Hook(this);
		this.hooks.onPageLayout = new Hook(this);
		this.hooks.layout = new Hook(this);
		this.hooks.renderNode = new Hook(this);
		this.hooks.layoutNode = new Hook(this);
		this.hooks.onOverflow = new Hook(this);
		this.hooks.afterOverflowRemoved = new Hook(this);
		this.hooks.onBreakToken = new Hook();
		this.hooks.beforeRenderResult = new Hook(this);
		this.hooks.afterPageLayout = new Hook(this);
		this.hooks.finalizePage = new Hook(this);
		this.hooks.afterRendered = new Hook(this);

		this.pages = [];
		this.total = 0;

		this.q = new Queue(this);
		this.stopped = false;
		this.rendered = false;

		this.content = content;

		this.charsPerBreak = [];
		this.maxChars;

		if (content) {
			this.flow(content, renderTo);
		}
	}

	async flow(content, renderTo) {
		let parsed;

		await this.hooks.beforeParsed.trigger(content, this);
		// 为每个节点生成data-ref，content为编写内容
		parsed = new ContentParser(content);

		this.hooks.filter.triggerSync(parsed);

		this.source = parsed;
		this.breakToken = undefined;

		if (this.pagesArea && this.pageTemplate) {
			this.q.clear();
			this.removePages();
		} else {
			this.setup(renderTo);
		}

		// 通知正在渲染，将解析的dom发送出去
		this.emit("rendering", parsed);

		await this.hooks.afterParsed.trigger(parsed, this);

		// 加载字体
		await this.loadFonts();

		let rendered = await this.render(parsed, this.breakToken);

		while (rendered.canceled) {
			this.start();
			rendered = await this.render(parsed, this.breakToken);
		}

		this.rendered = true;
		this.pagesArea.style.setProperty("--pagedjs-page-count", this.total);

		await this.hooks.afterRendered.trigger(this.pages, this);

		this.emit("rendered", this.pages);

		return this;
	}

	/**
	 * 设置
	 * 1. 创建一个div pagesArea带有类样式pagedjs_pages，默认插入到body中
	 * 2. 创建pageTemplate模板
	 * @param {HTMLElement} renderTo 渲染位置
	 * @returns {void}
	 */
	setup(renderTo) {
		this.pagesArea = document.createElement("div");
		this.pagesArea.classList.add("pagedjs_pages");

		if (renderTo) {
			renderTo.appendChild(this.pagesArea);
		} else {
			document.querySelector("body").appendChild(this.pagesArea);
		}

		this.pageTemplate = document.createElement("template");
		this.pageTemplate.innerHTML = TEMPLATE;
	}

	// oversetPages() {
	// 	let overset = [];
	// 	for (let i = 0; i < this.pages.length; i++) {
	// 		let page = this.pages[i];
	// 		if (page.overset) {
	// 			overset.push(page);
	// 			// page.overset = false;
	// 		}
	// 	}
	// 	return overset;
	// }
	//
	// async handleOverset(parsed) {
	// 	let overset = this.oversetPages();
	// 	if (overset.length) {
	// 		console.log("overset", overset);
	// 		let index = this.pages.indexOf(overset[0]) + 1;
	// 		console.log("INDEX", index);
	//
	// 		// Remove pages
	// 		// this.removePages(index);
	//
	// 		// await this.render(parsed, overset[0].overset);
	//
	// 		// return this.handleOverset(parsed);
	// 	}
	// }

	async render(parsed, startAt) {
		let renderer = this.layout(parsed, startAt);

		let done = false;
		let result;

		let loops = 0;
		while (!done) {
			result = await this.q.enqueue(() => {
				return this.renderAsync(renderer);
			});
			done = result.done;
			if (MAX_LAYOUTS) {
				loops += 1;
				if (loops >= MAX_LAYOUTS) {
					this.stop();
					break;
				}
			}
		}

		return result;
	}

	/**
	 *
	 * @param {*} content 就是parsed已解析的dom
	 * @param {*} startAt
	 * @returns {AsyncGenerator} AsyncGenerator
	 */
	async *layout(content, startAt) {
		let breakToken = startAt || false;
		let tokens = [];

		console.log("breakToken::", breakToken);
		console.log("content.firstChild::", content.firstChild);

		while (
			breakToken !== undefined &&
			(MAX_PAGES ? this.total < MAX_PAGES : true)
		) {
			if (breakToken && breakToken.node) {
				await this.handleBreaks(breakToken.node);
			} else {
				await this.handleBreaks(content.firstChild);
			}

			let page = this.addPage();

			await this.hooks.beforePageLayout.trigger(
				page,
				content,
				breakToken,
				this
			);
			this.emit("page", page);

			// Layout content in the page, starting from the breakToken
			breakToken = await page.layout(content, breakToken, this.maxChars);

			if (breakToken) {
				let newToken = breakToken.toJSON(true);
				if (tokens.lastIndexOf(newToken) > -1) {
					// loop
					let err = new OverflowContentError("Layout repeated", [
						breakToken.node,
					]);
					console.error("Layout repeated at: ", breakToken.node);
					return err;
				} else {
					tokens.push(newToken);
				}
			}

			await this.hooks.afterPageLayout.trigger(
				page.element,
				page,
				breakToken,
				this
			);
			await this.hooks.finalizePage.trigger(
				page.element,
				page,
				undefined,
				this
			);
			this.emit("renderedPage", page);

			this.recoredCharLength(page.wrapper.textContent.length);

			yield breakToken;

			// Stop if we get undefined, showing we have reached the end of the content
		}
	}

	async handleBreaks(node, force) {
		console.log("node:", node);
		console.log("total:", this.total);
		let currentPage = this.total + 1;
		let currentPosition = currentPage % 2 === 0 ? "left" : "right";
		// TODO: Recto and Verso should reverse for rtl languages. 对于rtl 语言，右边页，左面页应该互换
		let currentSide = currentPage % 2 === 0 ? "verso" : "recto";
		let previousBreakAfter;
		let breakBefore;
		let page;

		if (currentPage === 1) {
			return;
		}

		if (
			node &&
			typeof node.dataset !== "undefined" &&
			typeof node.dataset.previousBreakAfter !== "undefined"
		) {
			previousBreakAfter = node.dataset.previousBreakAfter;
		}

		if (
			node &&
			typeof node.dataset !== "undefined" &&
			typeof node.dataset.breakBefore !== "undefined"
		) {
			breakBefore = node.dataset.breakBefore;
		}

		if (force) {
			page = this.addPage(true);
		} else if (
			previousBreakAfter &&
			(previousBreakAfter === "left" || previousBreakAfter === "right") &&
			previousBreakAfter !== currentPosition
		) {
			page = this.addPage(true);
		} else if (
			previousBreakAfter &&
			(previousBreakAfter === "verso" || previousBreakAfter === "recto") &&
			previousBreakAfter !== currentSide
		) {
			page = this.addPage(true);
		} else if (
			breakBefore &&
			(breakBefore === "left" || breakBefore === "right") &&
			breakBefore !== currentPosition
		) {
			page = this.addPage(true);
		} else if (
			breakBefore &&
			(breakBefore === "verso" || breakBefore === "recto") &&
			breakBefore !== currentSide
		) {
			page = this.addPage(true);
		}

		if (page) {
			await this.hooks.beforePageLayout.trigger(
				page,
				undefined,
				undefined,
				this
			);
			this.emit("page", page);
			// await this.hooks.layout.trigger(page.element, page, undefined, this);
			await this.hooks.afterPageLayout.trigger(
				page.element,
				page,
				undefined,
				this
			);
			await this.hooks.finalizePage.trigger(
				page.element,
				page,
				undefined,
				this
			);
			this.emit("renderedPage", page);
		}
	}

	start() {
		this.rendered = false;
		this.stopped = false;
	}

	stop() {
		this.stopped = true;
		// this.q.clear();
	}

	renderOnIdle(renderer) {
		return new Promise((resolve) => {
			requestIdleCallback(async () => {
				if (this.stopped) {
					return resolve({ done: true, canceled: true });
				}
				let result = await renderer.next();
				if (this.stopped) {
					resolve({ done: true, canceled: true });
				} else {
					resolve(result);
				}
			});
		});
	}

	async renderAsync(renderer) {
		if (this.stopped) {
			return { done: true, canceled: true };
		}
		let result = await renderer.next();
		if (this.stopped) {
			return { done: true, canceled: true };
		} else {
			return result;
		}
	}

	recoredCharLength(length) {
		if (length === 0) {
			return;
		}

		this.charsPerBreak.push(length);

		// Keep the length of the last few breaks
		if (this.charsPerBreak.length > 4) {
			this.charsPerBreak.shift();
		}

		this.maxChars =
			this.charsPerBreak.reduce((a, b) => a + b, 0) / this.charsPerBreak.length;
	}

	/**
	 *  删除页从index 默认从0开始删除出所有
	 *
	 * @param {number} fromIndex 从当前index开始删除改变原pages
	 * @returns {undefined} void
	 */
	removePages(fromIndex = 0) {
		if (fromIndex >= this.pages.length) {
			return;
		}

		// Remove pages
		for (let i = fromIndex; i < this.pages.length; i++) {
			this.pages[i].destroy();
		}

		if (fromIndex > 0) {
			this.pages.splice(fromIndex);
		} else {
			this.pages = [];
		}

		this.total = this.pages.length;
	}

	/**
	 *  添加页
	 * @param {boolean} blank 是否添加空白页
	 * @returns {page} 返回 创建的page
	 */
	addPage(blank) {
		// 取最后一页
		let lastPage = this.pages[this.pages.length - 1];
		// 从模板创建一个新页 Create a new page from the template. 
		let page = new Page(
			this.pagesArea,
			this.pageTemplate,
			blank,
			this.hooks,
			this.settings
		);

		this.pages.push(page);

		// 创建页面 Create the pages. 
		page.create(undefined, lastPage && lastPage.element);
		// 给页添加index
		page.index(this.total);

		if (!blank) {
			// 非空白页监听页面溢出 Listen for page overflow. 
			page.onOverflow((overflowToken) => {
				console.warn("overflow on", page.id, overflowToken);

				// 只有正在渲染时重新排版 Only reflow while rendering. 
				if (this.rendered) {
					return;
				}

				let index = this.pages.indexOf(page) + 1;

				// 停止渲染 Stop the rendering
				this.stop();

				// Set the breakToken to resume at
				this.breakToken = overflowToken;

				// 移除页面 Remove pages
				this.removePages(index);

				if (this.rendered === true) {
					this.rendered = false;

					this.q.enqueue(async () => {
						this.start();

						await this.render(this.source, this.breakToken);

						this.rendered = true;
					});
				}
			});

			page.onUnderflow((overflowToken) => {
				// console.log("underflow on", page.id, overflowToken);
				// page.append(this.source, overflowToken);
			});
		}

		// 页面总数为pages的长度
		this.total = this.pages.length;

		return page;
	}

	/*
	insertPage(index, blank) {
		let lastPage = this.pages[index];
		// Create a new page from the template
		let page = new Page(this.pagesArea, this.pageTemplate, blank, this.hooks);

		let total = this.pages.splice(index, 0, page);

		// Create the pages
		page.create(undefined, lastPage && lastPage.element);

		page.index(index + 1);

		for (let i = index + 2; i < this.pages.length; i++) {
			this.pages[i].index(i);
		}

		if (!blank) {
			// Listen for page overflow
			page.onOverflow((overflowToken) => {
				if (total < this.pages.length) {
					this.pages[total].layout(this.source, overflowToken);
				} else {
					let newPage = this.addPage();
					newPage.layout(this.source, overflowToken);
				}
			});

			page.onUnderflow(() => {
				// console.log("underflow on", page.id);
			});
		}

		this.total += 1;

		return page;
	}
	*/

	async clonePage(originalPage) {
		let lastPage = this.pages[this.pages.length - 1];

		let page = new Page(this.pagesArea, this.pageTemplate, false, this.hooks);

		this.pages.push(page);

		// Create the pages
		page.create(undefined, lastPage && lastPage.element);

		page.index(this.total);

		await this.hooks.beforePageLayout.trigger(page, undefined, undefined, this);
		this.emit("page", page);

		for (const className of originalPage.element.classList) {
			if (
				className !== "pagedjs_left_page" &&
				className !== "pagedjs_right_page"
			) {
				page.element.classList.add(className);
			}
		}

		await this.hooks.afterPageLayout.trigger(
			page.element,
			page,
			undefined,
			this
		);
		await this.hooks.finalizePage.trigger(page.element, page, undefined, this);
		this.emit("renderedPage", page);
	}

	/**
	 * 加载字体
	 * @returns {Promise<string[]>} 返回promise
	 */
	loadFonts() {
		let fontPromises = [];
		(document.fonts || []).forEach((fontFace) => {
			if (fontFace.status !== "loaded") {
				let fontLoaded = fontFace.load().then(
					(r) => {
						return fontFace.family;
					},
					(r) => {
						console.warn("Failed to preload font-family:", fontFace.family);
						return fontFace.family;
					}
				);
				fontPromises.push(fontLoaded);
			}
		});
		return Promise.all(fontPromises).catch((err) => {
			console.warn(err);
		});
	}

	destroy() {
		this.pagesArea.remove();
		this.pageTemplate.remove();
	}
}

EventEmitter(Chunker.prototype);

export default Chunker;
